/*
    This file is part of QTau
    Copyright (C) 2013-2018  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/

#define HAVE_DYNDRAWER
//this will be fixed for next release

#include "mainwindow.h"
#include "qformlayout.h"
#include "ui_mainwindow.h"

#include <QIcon>
#include <QtCore>
#include <QtGui>

#include "Controller.h"
#include "Session.h"

#include <QComboBox>
#include <QDial>
#include <QFileDialog>
#include <QGridLayout>
#include <QGroupBox>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QScrollBar>
#include <QSplitter>
#include <QTabWidget>
#include <QTextEdit>
#include <QToolBar>
#include <QHeaderView>

#include "ui/Config.h"
#include "ui/dynDrawer.h"
#include "ui/meter.h"
#include "ui/noteEditor.h"
#include "ui/piano.h"

#include "tempomap.h"

#include "ui/tempodialog.h"
#include <QTableView>
#include "dyntablemodel.h"

#define __devloglevel__ 8

const int cdef_bars = 128;  // 128 bars "is enough for everyone" // TODO: make dynamic

const int c_piano_min_width = 110;
const int c_piano_min_height = 40;
const int c_meter_min_height = 40;
const int c_waveform_min_height = 50;
const int c_drawzone_min_height = 100;
const int c_dynbuttons_num = 12;

const QString c_dynlbl_css_off =
    QString("QLabel { color : %1; }").arg(cdef_color_dynbtn_off);
const QString c_dynlbl_css_bg =
    QString("QLabel { color : %1; }").arg(cdef_color_dynbtn_bg);
const QString c_dynlbl_css_fg =
    QString("QLabel { color : %1; background-color : %2; }")
    .arg(cdef_color_dynbtn_on)
    .arg(cdef_color_dynbtn_on_bg);

QSettings settings("QTau", c_qtau_name);

const QString c_key_dir_score = QStringLiteral("last_score_dir");
const QString c_key_dir_audio = QStringLiteral("last_audio_dir");
const QString c_key_win_size = QStringLiteral("window_size");
const QString c_key_win_max = QStringLiteral("window_fullscreen");
const QString c_key_show_lognum = QStringLiteral("show_new_log_number");
const QString c_key_dynpanel_on = QStringLiteral("dynamics_panel_visible");
const QString c_key_sound = QStringLiteral("sould_level");
const QString c_key_audio_codec = QStringLiteral("save_audio_codec");

const QString c_doc_txt = QStringLiteral(":/tr/documentation_en.txt");
const QString c_icon_app = QStringLiteral(":/images/appicon_ouka_alice.png");
const QString c_icon_sound = QStringLiteral(":/images/speaker.png");
const QString c_icon_mute = QStringLiteral(":/images/speaker_mute.png");
const QString c_icon_editor = QStringLiteral(":/images/b_notes.png");
const QString c_icon_voices = QStringLiteral(":/images/b_mic.png");
const QString c_icon_plugins = QStringLiteral(":/images/b_plug.png");
const QString c_icon_settings = QStringLiteral(":/images/b_gear.png");
const QString c_icon_doc = QStringLiteral(":/images/b_manual.png");
const QString c_icon_log = QStringLiteral(":/images/b_envelope.png");
const QString c_icon_play = QStringLiteral(":/images/b_play.png");
const QString c_icon_jack = QStringLiteral(":/images/jack-transport.png");
const QString c_icon_pause = QStringLiteral(":/images/b_pause.png");

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      _logNewMessages(0),
      _logHasErrors(false),
      _showNewLogNumber(true) {
    _ns.tmap = new TempoMap();
    _ns.tmap->addTempo(0, 120);
    _ns.tmap->addTimeSignature(0, 4, 4);  // default values
    _ns.barScreenOffsets = new int[1024];
    _drawZone = nullptr;

    ui->setupUi(this);

    setWindowIcon(QIcon(c_icon_app));
    setWindowTitle(c_qtau_name);
    setAcceptDrops(true);
    setContextMenuPolicy(Qt::NoContextMenu);

    //-----------------------------------------
    _mbe = new MeterBarLineEdit();
    _mbe->setText("");
    _mbl = new QLabel("");

    // QLabel* labelHW = new QLabel("HelloWorld");
    _mbe->setFixedSize(110, 20);

    _meter = new qtauMeterBar(this);
    _meter->setMinimumHeight(c_meter_min_height);

    _meter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
    _meter->setContentsMargins(0, 0, 0, 0);

    _piano = new qtauPiano(ui->centralWidget);
    _piano->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
    _piano->setMinimumSize(c_piano_min_width, c_piano_min_height);
    _piano->setContentsMargins(0, 0, 0, 0);

    _zoom = new QSlider(Qt::Horizontal, ui->centralWidget);
    _zoom->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
    _zoom->setRange(0, c_zoom_num - 1);
    _zoom->setSingleStep(1);
    _zoom->setPageStep(1);
    _zoom->setValue(cdef_zoom_index);
    _zoom->setMinimumWidth(c_piano_min_width);
    _zoom->setGeometry(0, 0, c_piano_min_width, 10);
    _zoom->setContentsMargins(0, 0, 0, 0);

    _noteEditor = new qtauNoteEditor(ui->centralWidget);
    _noteEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    _noteEditor->setContentsMargins(0, 0, 0, 0);

    _hscr = new QScrollBar(Qt::Horizontal, ui->centralWidget);
    _vscr = new QScrollBar(Qt::Vertical, ui->centralWidget);

    _hscr->setContentsMargins(0, 0, 0, 0);
    _vscr->setContentsMargins(0, 0, 0, 0);
    _hscr->setRange(0, _ns.note.width() * 4 * cdef_bars);  // FIXXME
    _vscr->setRange(0, _ns.note.height() * 12 * _ns.numOctaves);
    _hscr->setSingleStep(_ns.note.width());
    _vscr->setSingleStep(_ns.note.height());
    _hscr->setContextMenuPolicy(Qt::NoContextMenu);
    _vscr->setContextMenuPolicy(Qt::NoContextMenu);

    updateSetup();

    //---- vocal and music waveform panels, hidden until synthesized (vocal wave)
    //and/or loaded (music wave)

    QScrollBar *dummySB = new QScrollBar(this);
    dummySB->setOrientation(Qt::Vertical);
    dummySB->setRange(0, 0);
    dummySB->setEnabled(false);

    QFrame *waveControls = new QFrame(this);
    waveControls->setContentsMargins(0, 0, 0, 0);
    waveControls->setMinimumSize(c_piano_min_width, c_waveform_min_height);
    waveControls->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
    waveControls->setFrameStyle(QFrame::Panel | QFrame::Raised);

    QGridLayout *waveformL = new QGridLayout();
    waveformL->setContentsMargins(0, 0, 0, 0);
    waveformL->setSpacing(0);
    waveformL->addWidget(waveControls, 0, 0, 2, 1);
    waveformL->addWidget(dummySB, 0, 2, 2, 1);

    _wavePanel = new QWidget(this);
    _wavePanel->setContentsMargins(0, 0, 0, 0);
    _wavePanel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
    _wavePanel->setLayout(waveformL);
    _wavePanel->setVisible(false);

    //---- notes' dynamics setup area --------------



#if 0
     QGridLayout *dynBtnL = new QGridLayout();
    QString btnNames[c_dynbuttons_num] = {"VEL", "DYN", "BRE", "BRI",
                                          "CLE", "OPE", "GEN", "POR",
                                          "PIT", "PBS", "XSY", "GWL"
                                         };
    //VEL: scalar
    //DYN: envelope
    //BRE: scalar ?
    //BRI: scalar ?

    //CLE: scalar ?
    //OPE: scalar ?
    //GEN: scalar *
    //POR: scalar

    //PIT: envelope
    //PBS: ?
    //XSY: ?
    //GWL ?

    for (int i = 0; i < c_dynbuttons_num; ++i) {
        qtauDynLabel *l = new qtauDynLabel(btnNames[i], this);
        l->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
        dynBtnL->addWidget(l, i / 2, i % 2, 1, 1);

        l->setStyleSheet(c_dynlbl_css_off);
        l->setFrameStyle(QFrame::Box);
        l->setLineWidth(1);

        connect(l, SIGNAL(leftClicked()), SLOT(dynBtnLClicked()));
        connect(l, SIGNAL(rightClicked()), SLOT(dynBtnRClicked()));
    }
    dynBtnL->setRowStretch(c_dynbuttons_num / 2, 100);
#endif



    QFrame *dynBtnPanel = new QFrame(this);
    dynBtnPanel->setContentsMargins(0, 0, 0, 0);
    dynBtnPanel->setMinimumSize(c_piano_min_width, c_drawzone_min_height);
    dynBtnPanel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
    dynBtnPanel->setFrameStyle(QFrame::Panel | QFrame::Raised);

    //dynBtnPanel->setLayout(dynBtnL);

    _drawZone = new qtauDynDrawer(_noteEditor, ui->centralWidget);
    _drawZone->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
    _drawZone->setMinimumHeight(c_drawzone_min_height);
    _drawZone->setContentsMargins(0, 0, 0, 0);
    _drawZone->configure(_ns);

    QScrollBar *dummySB3 = new QScrollBar(this);
    dummySB3->setOrientation(Qt::Vertical);
    dummySB3->setRange(0, 0);
    dummySB3->setEnabled(false);

    QHBoxLayout *singParamsL = new QHBoxLayout();
    singParamsL->setContentsMargins(0, 0, 0, 0);
    singParamsL->setSpacing(0);
    singParamsL->addWidget(dynBtnPanel);

    //only one singParamsL->addWidget(_drawZone);

    _dynTable= new QTableView(this);
    _dynTableModel = new DynTableModel();
    _dynTable->setModel(_dynTableModel);
    _dynTable->setEnabled(false);
    _dynTable->horizontalHeader()->setStretchLastSection(true);

    singParamsL->addWidget(_dynTable);
    singParamsL->addWidget(dummySB3);

    _drawZonePanel = new QWidget(this);
    _drawZonePanel->setContentsMargins(0, 0, 0, 0);
    _drawZonePanel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);

    _drawZonePanel->setLayout(singParamsL);

    //---- Combining editor panels into hi-level layout ------

    QGridLayout *gl = new QGridLayout();
    gl->setContentsMargins(0, 0, 0, 0);
    gl->setSpacing(0);

    QFrame *meterFrame = new QFrame(this);
    QVBoxLayout *meterLayout = new QVBoxLayout();

    meterLayout->setSpacing(0);
    meterLayout->setMargin(0);
    meterFrame->setMaximumSize(110, 40);
    meterFrame->setLayout(meterLayout);
    meterLayout->addWidget(_mbl);
    meterLayout->addWidget(_mbe);

    gl->addWidget(meterFrame, 0, 0, 1, 1);
    gl->addWidget(_meter, 0, 1, 1, 1);
    gl->addWidget(_piano, 1, 0, 1, 1);
    gl->addWidget(_zoom, 2, 0, 1, 1);
    gl->addWidget(_noteEditor, 1, 1, 1, 1);
    gl->addWidget(_hscr, 2, 1, 1, 1);
    gl->addWidget(_vscr, 1, 2, 1, 1);

    QWidget *editorUpperPanel = new QWidget(this);
    editorUpperPanel->setContentsMargins(0, 0, 0, 0);
    editorUpperPanel->setSizePolicy(QSizePolicy::Expanding,
                                    QSizePolicy::Expanding);
    editorUpperPanel->setMaximumSize(9000, 9000);

    editorUpperPanel->setLayout(gl);

    _editorSplitter = new QSplitter(Qt::Vertical, this);
    _editorSplitter->setContentsMargins(0, 0, 0, 0);
    _editorSplitter->addWidget(editorUpperPanel);

    _wavePanel->setVisible(false);  // keep wavepanel for now, but set hidden
    _editorSplitter->addWidget(_wavePanel);

#ifdef HAVE_DYNDRAWER
    _drawZonePanel->setVisible(true);
#else
    _drawZonePanel->setVisible(false);
#endif

    _editorSplitter->addWidget(_drawZonePanel);
    _editorSplitter->setStretchFactor(0, 1);
    _editorSplitter->setStretchFactor(1, 0);
    _editorSplitter->setStretchFactor(2, 0);
    _editorSplitter->setStretchFactor(3, 0);

    QList<int> sizes = _editorSplitter->sizes();
    sizes[1] = 0;
    _editorSplitter->setSizes(sizes);

    QVBoxLayout *edVBL = new QVBoxLayout();
    edVBL->setContentsMargins(0, 0, 0, 0);
    edVBL->addWidget(_editorSplitter);

    QWidget *editorPanel = new QWidget(this);
    editorPanel->setContentsMargins(0, 0, 0, 0);
    editorPanel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    editorPanel->setMaximumSize(9000, 9000);

    editorPanel->setLayout(edVBL);
    //---- Log tab ---------------------------------

    QWidget *logPanel = new QWidget(this);
    logPanel->setContentsMargins(0, 0, 0, 0);
    logPanel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    logPanel->setMaximumSize(9000, 9000);

    _logpad = new QTextEdit(this);
    _logpad->setReadOnly(true);
    _logpad->setUndoRedoEnabled(false);
    _logpad->setContextMenuPolicy(Qt::NoContextMenu);
    _logpad->setStyleSheet("p, pre { white-space: 1.2; }");

    QGridLayout *logL = new QGridLayout();
    logL->setContentsMargins(0, 0, 0, 0);
    logL->addWidget(_logpad, 0, 0, 1, 1);

    logPanel->setLayout(logL);

    //---- Combining tabs togeter ------------------

    _tabs = new QTabWidget(this);
    _tabs->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    _tabs->setContentsMargins(0, 0, 0, 0);
    _tabs->setMaximumSize(9000, 9000);
    _tabs->setTabPosition(QTabWidget::South);
    _tabs->setMovable(false);  // just to be sure

    _tabs->addTab(editorPanel, QIcon(c_icon_editor), tr("Editor"));
    _tabs->addTab(logPanel, QIcon(c_icon_log), tr("Log"));

    _tabs->widget(0)->setContentsMargins(0, 0, 0, 0);
    _tabs->widget(1)->setContentsMargins(0, 0, 0, 0);

    _logTabTextColor = _tabs->tabBar()->tabTextColor(1);

    connect(_tabs, SIGNAL(currentChanged(int)), SLOT(onTabSelected(int)));

    QVBoxLayout *vbl = new QVBoxLayout();
    vbl->setContentsMargins(0, 0, 0, 0);
    vbl->addWidget(_tabs);
    ui->centralWidget->setContentsMargins(0, 0, 0, 0);
    ui->centralWidget->setLayout(vbl);

    //---- Toolbars --------------------------------

    QToolBar *fileTB = new QToolBar("Fileops", this);
    QToolBar *playerTB = new QToolBar("Playback", this);
    QToolBar *toolsTB = new QToolBar("Toolset", this);

    fileTB->setFloatable(false);
    playerTB->setFloatable(false);
    toolsTB->setFloatable(false);

    fileTB->addAction(ui->actionSave);
    fileTB->addAction(ui->actionSave_audio_as);
    fileTB->addAction(ui->actionUndo);
    fileTB->addAction(ui->actionRedo);

    playerTB->addAction(ui->actionPlay);
    playerTB->addAction(ui->actionStop);
    playerTB->addAction(ui->actionBack);
    playerTB->addAction(ui->actionJack);

    QComboBox *quantizeCombo = new QComboBox(this);
    QComboBox *lengthCombo = new QComboBox(this);
    quantizeCombo->addItems(QStringList() << "Q/4"
                            << "Q/8"
                            << "Q/16"
                            << "Q/32"
                            << "Q/64");
    lengthCombo->addItems(QStringList() << "♪/4"
                          << "♪/8"
                          << "♪/16"
                          << "♪/32"
                          << "♪/64");
    quantizeCombo->setCurrentIndex(3);
    lengthCombo->setCurrentIndex(3);

    toolsTB->addAction(ui->actionEdit_Mode);
    toolsTB->addAction(ui->actionGrid_Snap);
    toolsTB->addSeparator();
    toolsTB->addWidget(quantizeCombo);
    toolsTB->addWidget(lengthCombo);

    toolsTB->addSeparator();
    toolsTB->addWidget(new QLabel("Singer: "));
    _singerSelect = new QComboBox();
    _singerSelect->setMinimumWidth(100);
    connect(_singerSelect, SIGNAL(currentIndexChanged(int)),
            SLOT(onSingerSelected(int)));
    toolsTB->addWidget(_singerSelect);

    for (int i = 0; i < MAXRECENTFILES; ++i) {
        _recentFileActs[i] = new QAction(this);
        _recentFileActs[i]->setVisible(false);
        this->ui->menuRecent_files->addAction(_recentFileActs[i]);
        connect(_recentFileActs[i], SIGNAL(triggered()), this,
                SLOT(openRecentFile()));
    }
    updateRecentFileActions();

    addToolBar(fileTB);
    addToolBar(playerTB);
    addToolBar(toolsTB);

    _toolbars.append(fileTB);
    _toolbars.append(playerTB);
    _toolbars.append(toolsTB);

    //----------------------------------------------
    connect(quantizeCombo, SIGNAL(currentIndexChanged(int)),
            SLOT(onQuantizeSelected(int)));
    connect(lengthCombo, SIGNAL(currentIndexChanged(int)),
            SLOT(onNotelengthSelected(int)));

    connect(_piano, &qtauPiano ::heightChanged, this,
            &MainWindow::onPianoHeightChanged);
    connect(_noteEditor, &qtauNoteEditor ::widthChanged, this,
            &MainWindow::onNoteEditorWidthChanged);

    connect(_meter, &qtauMeterBar ::scrolled, this, &MainWindow::notesHScrolled);
    connect(_piano, &qtauPiano ::scrolled, this, &MainWindow::notesVScrolled);
    connect(_drawZone, &qtauDynDrawer ::scrolled, this,
            &MainWindow::notesHScrolled);
    connect(_noteEditor, &qtauNoteEditor ::vscrolled, this,
            &MainWindow::notesVScrolled);
    connect(_noteEditor, &qtauNoteEditor ::hscrolled, this,
            &MainWindow::notesHScrolled);
    connect(_vscr, &QScrollBar ::valueChanged, this, &MainWindow::vertScrolled);
    connect(_hscr, &QScrollBar ::valueChanged, this, &MainWindow::horzScrolled);

    connect(_noteEditor, &qtauNoteEditor ::rmbScrolled, this,
            &MainWindow::onEditorRMBScrolled);
    connect(_noteEditor, &qtauNoteEditor ::requestsOffset, this,
            &MainWindow::onEditorRequestOffset);

    connect(_zoom, &QSlider ::valueChanged, this, &MainWindow::onZoomed);
    connect(_meter, &qtauMeterBar ::zoomed, this, &MainWindow::onEditorZoomed);
    connect(_noteEditor, &qtauNoteEditor ::zoomed, this,
            &MainWindow::onEditorZoomed);
    connect(_drawZone, &qtauDynDrawer ::zoomed, this,
            &MainWindow::onEditorZoomed);

    connect(ui->actionQuit, &QAction::triggered, [=]() {
        this->close();
    });
    connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onOpenUST);
    connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onSaveUST);
    connect(ui->actionSave_as, &QAction::triggered, this,
            &MainWindow::onSaveUSTAs);

    connect(ui->actionMIDI, &QAction::triggered, this, &MainWindow::onMIDIImport);
    connect(ui->actionMIDI_2, &QAction::triggered, this,
            &MainWindow::onMIDIExport);

    connect(ui->actionUndo, &QAction::triggered, this, &MainWindow::onUndo);
    connect(ui->actionRedo, &QAction::triggered, this, &MainWindow::onRedo);
    connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);

    connect(ui->actionEdit_Mode, &QAction::triggered, this,
            &MainWindow::onEditMode);
    connect(ui->actionGrid_Snap, &QAction::triggered, this,
            &MainWindow::onGridSnap);

    connect(_noteEditor,&qtauNoteEditor::selectionChanged,this,&MainWindow::onSelectionChanged);

    //----------------------------------------------

    _lastScoreDir = settings.value(c_key_dir_score, "").toString();
    _lastAudioDir = settings.value(c_key_dir_audio, "").toString();
    _audioExt = settings.value(c_key_audio_codec, "").toString();
    _showNewLogNumber = settings.value(c_key_show_lognum, true).toBool();

    bool jack_auto_connect = settings.value("jack_auto_connect",true).toBool();

    ui->actionConnect2Jack->setCheckable(true);
    ui->actionConnect2Jack->setChecked(jack_auto_connect);


    if (!settings.value(c_key_dynpanel_on, true).toBool()) {
        QList<int> panelSizes = _editorSplitter->sizes();
        panelSizes.last() = 0;
        _editorSplitter->setSizes(panelSizes);
    }

    if (settings.value(c_key_win_max, false).toBool())
        showMaximized();
    else {
        QRect winGeom = geometry();
        QRect setGeom =
            settings.value(c_key_win_size, QRect(winGeom.topLeft(), minimumSize()))
            .value<QRect>();

        if (setGeom.width() >= winGeom.width() &&
                setGeom.height() >= setGeom.height())
            setGeometry(setGeom);
    }

    //----------------------------------------------
    // -- hacks for debugging UI -- remove or refactor

    ui->actionPlay->setText(tr("Play"));
    ui->actionPlay->setIcon(QIcon(c_icon_play));
    ui->actionStop->setEnabled(true);
    ui->actionBack->setEnabled(true);
    ui->actionRepeat->setEnabled(false);
    ui->actionSave_audio_as->setEnabled(false);

    ui->actionPlay->setChecked(false);
    ui->actionRepeat->setChecked(false);

    ui->actionPlay->setEnabled(true);

    _menuPlugins = new QMenu("Plugins", this);
    this->ui->menuTools->addMenu(_menuPlugins);
}
void MainWindow::addPluginAction(QAction *action) {
    _menuPlugins->addAction(action);
}

void MainWindow::closeEvent(QCloseEvent *event) {
    // store settings
    settings.setValue(c_key_dir_score, _lastScoreDir);
    settings.setValue(c_key_dir_audio, _lastAudioDir);
    settings.setValue(c_key_win_size, geometry());
    settings.setValue(c_key_win_max, isMaximized());
    settings.setValue(c_key_show_lognum, _showNewLogNumber);
    settings.setValue(c_key_audio_codec, _audioExt);
    settings.setValue(c_key_dynpanel_on, _editorSplitter->sizes().last() > 0);

    event->accept();
}

MainWindow::~MainWindow() {
    delete ui;
}

//========================================================================================

bool MainWindow::setController(qtauController &c, qtauSession &s) {
    // NOTE: bind what uses qtauSession only here (menu/toolbar button states etc)
    _doc = &s;
    _ctrl = &c;

    connect(_noteEditor, &qtauNoteEditor::editorEvent, _doc,
            &qtauSession::onUIEvent);

    connect(_dynTableModel, &DynTableModel::noteEffect, _doc,
            &qtauSession::onUIEvent);

    connect(&c, &qtauController::transportPositionChanged, this,
            &MainWindow::onTransportPositionChanged);

    connect(ui->actionNew, &QAction::triggered, &s, &qtauSession::onNewSession);

    connect(_doc, &qtauSession::dataReloaded, this, &MainWindow::onDocReloaded);
    connect(_doc, &qtauSession::modifiedStatus, this, &MainWindow::onDocStatus);
    connect(_doc, &qtauSession::undoStatus, this, &MainWindow::onUndoStatus);
    connect(_doc, &qtauSession::redoStatus, this, &MainWindow::onRedoStatus);

    connect(_doc, &qtauSession::onEvent, this, &MainWindow::onDocEvent);

    connect(ui->actionPlay, &QAction::triggered, &c,
            &qtauController::onRequestStartPlayback);
    connect(ui->actionStop, &QAction::triggered, &c,
            &qtauController::onRequestStopPlayback);
    connect(ui->actionBack, &QAction::triggered, &c,
            &qtauController::onRequestResetPlayback);

    connect(this, &MainWindow::loadUST, &c, &qtauController::onLoadUST);
    connect(this, &MainWindow::saveUST, &c, &qtauController::onSaveUST);

    connect(_piano, &qtauPiano::keyPressed, &c, &qtauController::pianoKeyPressed);
    connect(_piano, &qtauPiano::keyReleased, &c,
            &qtauController::pianoKeyReleased);

    connect(ui->actionJack, &QAction::triggered, this,
            &MainWindow::onActionJackTriggered);

    connect(_meter, &qtauMeterBar::barClicked, this,
            &MainWindow::meter_barClicked);
    connect(_mbe, &MeterBarLineEdit::keyPressHooked, this,
            &MainWindow::mbe_keyPressHooked);

    connect(ui->actionOpen_Voice_Directory,&QAction::triggered, &c,
            &qtauController::openVoiceDirectory);
    connect(ui->actionRescan_Voice_Directory,&QAction::triggered, &c,
            &qtauController::rescanVoiceDirectory);
    connect(&c,&qtauController::voiceListChanged,this,&MainWindow::voiceListChanged);
    //-----------------------------------------------------------------------

    // widget configuration - maybe read app settings here?
    _noteEditor->setRMBScrollEnabled(!ui->actionEdit_Mode->isChecked());
    _noteEditor->setEditingEnabled(ui->actionEdit_Mode->isChecked());
    _noteEditor->setFocus();

    if(c.needDownload()) onLog("no voice is installed, qtau will install defa",ELog::info);

    voiceListChanged();

    return true;
}

void MainWindow::voiceListChanged()
{
    _singerSelect->clear();
    foreach (QString voice, _ctrl->voices()) {
        _singerSelect->addItem(voice);
    }
}

void MainWindow::updateUndoRedoTexts() {
    if (_doc->canUndo())
        ui->actionUndo->setText("Undo " + _doc->undoAction());
    else
        ui->actionUndo->setText("Undo");
    if (_doc->canRedo())
        ui->actionRedo->setText("Redo " + _doc->redoAction());
    else
        ui->actionRedo->setText("Redo");
}

void MainWindow::onOpenUST() {
    QString fileName = QFileDialog::getOpenFileName(
                           this, tr("Open USTJ"), _lastScoreDir,
                           tr("UTAU JSON Sequence Text Files (*.ustj)"));

    if (!fileName.isEmpty()) {
        _lastScoreDir = QFileInfo(fileName).absolutePath();
        emit loadUST(fileName);
    }
}

void MainWindow::onSaveUST() {
    if (_doc->documentFile().isEmpty())
        onSaveUSTAs();
    else
        emit saveUST(_doc->documentFile(), true);
}

void MainWindow::onSaveUSTAs() {
    QString fileName = QFileDialog::getSaveFileName(
                           this, tr("Save USTJ"), _lastScoreDir,
                           tr("UTAU JSON Sequence Text Files (*.ustj)"));
    if (!fileName.isEmpty()) {
        if (!fileName.endsWith(".ustj")) fileName += ".ustj";
        _lastScoreDir = QFileInfo(fileName).absolutePath();
        emit saveUST(fileName, true);
    }
}

void MainWindow::onTransportPositionChanged(float pos) {
    _noteEditor->setPlaybackPosition(pos * 480);  // FIXME: do not hardcode utau
}

void MainWindow::onMIDIImport() {
    QString dir = settings.value("last_midi_dir","/tmp").toString();
    QString fileName =
        QFileDialog::getOpenFileName(this, tr("Import MIDI"),
                                     dir,  // FIXME do not hardcode
                                     tr("MIDI Files (*.mid *.mid *.smf)"));
    if(fileName.length()) {
        QFileInfo f(fileName);
        dir = f.dir().path();
        settings.setValue("last_midi_dir",dir);
        _doc->setDocName("imported "+f.fileName());
        _doc->importMIDI(fileName);
    }


}

void MainWindow::onMIDIExport() {
    QString dir = settings.value("last_midi_dir","/tmp").toString();
    QString fileName =
        QFileDialog::getSaveFileName(this, tr("Export as MIDI"), dir,
                                     tr("MIDI Files (*.mid *.mid *.smf)"));
    if (!fileName.endsWith(".mid")) fileName += ".mid";

    if(fileName.length()) {
        QFileInfo f(fileName);
        dir = f.dir().path();
        settings.setValue("last_midi_dir",dir);
        _doc->exportMIDI(fileName);
    }

}

void MainWindow::notesVScrolled(int delta) {
    if (delta > 0 && _vscr->value() > 0)  // scroll up
        delta = -_ns.note.height();
    else if (delta < 0 && _vscr->value() < _vscr->maximum())  // scroll down
        delta = _ns.note.height();
    else
        delta = 0;

    if (delta != 0) _vscr->setValue(_vscr->value() + delta);
}

void MainWindow::notesHScrolled(int delta) {
    if (delta > 0 && _hscr->value() > 0)  // scroll left
        delta = -_ns.note.width();
    else if (delta < 0 && _hscr->value() < _hscr->maximum())  // scroll right
        delta = _ns.note.width();
    else
        delta = 0;

    if (delta != 0) _hscr->setValue(_hscr->value() + delta);
}

void MainWindow::vertScrolled(int delta) {
    _piano->setOffset(delta);
    _noteEditor->setVOffset(delta);
}

void MainWindow::horzScrolled(int delta) {
    _noteEditor->setHOffset(delta);
    _meter->setOffset(delta);
    _drawZone->setOffset(delta);
}

void MainWindow::onEditorRMBScrolled(QPoint mouseDelta, QPoint origOffset) {
    // moving editor space in reverse of mouse delta
    int hOff = qMax(qMin(origOffset.x() - mouseDelta.x(), _hscr->maximum()), 0);
    int vOff = qMax(qMin(origOffset.y() - mouseDelta.y(), _vscr->maximum()), 0);

    _hscr->setValue(hOff);
    _vscr->setValue(vOff);
}

void MainWindow::onEditorRequestOffset(QPoint off) {
    off.setX(qMax(qMin(off.x(), _hscr->maximum()), 0));
    off.setY(qMax(qMin(off.y(), _vscr->maximum()), 0));

    _hscr->setValue(off.x());
    _vscr->setValue(off.y());
}

void MainWindow::onPianoHeightChanged(int newHeight) {
    _vscr->setMaximum(_ns.note.height() * 12 * _ns.numOctaves - newHeight + 1);
    _vscr->setPageStep(_piano->geometry().height());
}

void MainWindow::onNoteEditorWidthChanged(int newWidth) {
    //_hscr->setMaximum(_ns.note.width() * _ns.notesInBar * cdef_bars - newWidth +
    //1);
    _hscr->setMaximum(_ns.note.width() * 4 * cdef_bars - newWidth + 1);
    _hscr->setPageStep(_noteEditor->geometry().width());
}

void MainWindow::onUndo() {
    if (_doc->canUndo())
        _doc->undo();
    else
        ui->actionUndo->setEnabled(false);

    updateUndoRedoTexts();
}

void MainWindow::onRedo() {
    if (_doc->canRedo())
        _doc->redo();
    else
        ui->actionRedo->setEnabled(false);
    updateUndoRedoTexts();
}

void MainWindow::onDelete() {
    _noteEditor->deleteSelected();
}

void MainWindow::onEditMode(bool toggled) {
    _noteEditor->setEditingEnabled(toggled);
    _noteEditor->setRMBScrollEnabled(!toggled);
}

void MainWindow::onGridSnap(bool toggled) {
    _noteEditor->setGridSnapEnabled(toggled);
}

void MainWindow::onQuantizeSelected(int index) {
    int newQuant = 4 * (int)(pow(2, index) + 0.001);

    if (newQuant != _ns.quantize) {
        _ns.quantize = newQuant;
        _noteEditor->configure(_ns);
    }
}

void MainWindow::onNotelengthSelected(int index) {
    int newNoteLength = 4 * (int)(pow(2, index) + 0.001);

    if (newNoteLength != _ns.length) {
        _ns.length = newNoteLength;
        _noteEditor->configure(_ns);
    }
}

void MainWindow::dynBtnLClicked() {
    qtauDynLabel *l = qobject_cast<qtauDynLabel *>(sender());

    if (l && (_fgDynLbl == 0 || l != _fgDynLbl)) {
        if (_fgDynLbl) {
            _fgDynLbl->setState(qtauDynLabel::off);
            _fgDynLbl->setStyleSheet(c_dynlbl_css_off);
        }

        if (l == _bgDynLbl) {
            _bgDynLbl->setState(qtauDynLabel::off);
            _bgDynLbl->setStyleSheet(c_dynlbl_css_off);
            _bgDynLbl = 0;
        }

        l->setStyleSheet(c_dynlbl_css_fg);
        _fgDynLbl = l;
    }
}

void MainWindow::dynBtnRClicked() {
    qtauDynLabel *l = qobject_cast<qtauDynLabel *>(sender());

    if (l) {
        if (_bgDynLbl != 0 && l == _bgDynLbl) {
            // clicking on same dynkey - switch it off
            _bgDynLbl->setState(qtauDynLabel::off);
            _bgDynLbl->setStyleSheet(c_dynlbl_css_off);
            _bgDynLbl = 0;
        } else {            // clicking on other dynkey
            if (_bgDynLbl) {  // switch off previous one, if any
                _bgDynLbl->setState(qtauDynLabel::off);
                _bgDynLbl->setStyleSheet(c_dynlbl_css_off);
                _bgDynLbl = 0;
            }

            if (l != _fgDynLbl) {  // clicking on not-foreground dynkey
                l->setStyleSheet(c_dynlbl_css_bg);
                _bgDynLbl = l;
            }
        }
    }
}

void MainWindow::onLog(const QString &msg, ELog type) {
    QString color = "black";
    bool viewingLog = _tabs->currentIndex() == _tabs->count() - 1;

    switch (type) {
    case ELog::error:
        color = "red";
        break;
    case ELog::success:
        color = "green";
        break;
    default:
        break;
    }

    if (!viewingLog) {
        QTabBar *tb = const_cast<QTabBar *>(
                          _tabs->tabBar());  // dirty hack I know, but no other way atm

        if (_showNewLogNumber) {
            tb->setTabText(tb->count() - 1,
                           tr("Log") + QString(" (%1)").arg(_logNewMessages));
            _logNewMessages++;
        }

        if (type == ELog::error) {
            tb->setTabTextColor(tb->count() - 1, QColor(cdef_color_logtab_err));
            _logHasErrors = true;
        }
    }

    _logpad->moveCursor(QTextCursor::End);
    _logpad->insertHtml(
        QString("<pre style=\"color: %1;\">%2</pre><p></p>").arg(color).arg(msg));
}

void MainWindow::enableToolbars(bool enable) {
    foreach (QToolBar *t, _toolbars)
        t->setVisible(enable);  // t->setEnabled(enable);
}

void MainWindow::onTabSelected(int index) {
    enableToolbars(index == 0);

    if (index == _tabs->count() - 1) {
        QTabBar *tb = const_cast<QTabBar *>(_tabs->tabBar());

        if (_logNewMessages > 0) {
            tb->setTabText(tb->count() - 1, tr("Log"));
            _logNewMessages = 0;
        }

        if (_logHasErrors) {
            tb->setTabTextColor(tb->count() - 1,
                                _logTabTextColor);  // set default color back
            _logHasErrors = false;
        }
    }
}

void MainWindow::onZoomed(int z) {
    // modify note data and send it to widgets
    _ns.note.setWidth(c_zoom_note_widths[z]);

    _meter->configure2(_ns);
    _piano->configure(_ns);
    _noteEditor->configure(_ns);
    _drawZone->configure(_ns);

    // modify scrollbar sizes and position
    double hscr_val = (double)_hscr->value() / _hscr->maximum();
    _hscr->setMaximum(_ns.note.width() * 4 * cdef_bars - _noteEditor->width() +
                      1);
    _hscr->setValue(_hscr->maximum() * hscr_val);
    _hscr->setSingleStep(_ns.note.width());

    horzScrolled(_hscr->value());
}

void MainWindow::onEditorZoomed(int delta) {
    if (delta != 0)
        if ((delta > 0 && _zoom->value() >= 0) ||
                (delta < 0 && _zoom->value() < c_zoom_num))
            _zoom->setValue(_zoom->value() + ((delta > 0) ? 1 : -1));
}

void MainWindow::onDocReloaded() {
    setWindowTitle(_doc->documentName() + " - QTau");
    _noteEditor->reset();
}

void MainWindow::onDocStatus(bool isModified) {
    QString newDocName = _doc->documentName();

    if (_docName != newDocName) _docName = newDocName;

    setWindowTitle((isModified ? "*" : "") + _docName + " - QTau");

    ui->actionSave->setEnabled(isModified);
}

void MainWindow::onUndoStatus(bool canUndo) {
    ui->actionUndo->setEnabled(canUndo);
    updateUndoRedoTexts();
}

void MainWindow::onRedoStatus(bool canRedo) {
    ui->actionRedo->setEnabled(canRedo);
    updateUndoRedoTexts();
}

void MainWindow::onDocEvent(qtauEvent *event) {
    // FIXME: why here
    QString singerName = _doc->getSingerName();
    if (singerName.length()) {
        int count = _singerSelect->count();
        for (int i = 0; i < count; i++) {
            if (singerName == _singerSelect->itemText(i)) {
                _singerSelect->setCurrentIndex(i);
                _ctrl->selectSinger(singerName);
                break;
            }
        }
    }

    QJsonArray arr = _doc->getTempoMap();
    if (arr.size()) {
        _ns.tmap->fromJson(arr);
        DEVLOG_DEBUG("update setup from json");
        updateSetup();
    }

    if (event->type() >= ENoteEvents::add && event->type() <= ENoteEvents::effect)
        _noteEditor->onEvent(event);
    else
        DEVLOG_ERROR("bad event");
}

void MainWindow::dragEnterEvent(QDragEnterEvent *event) {
    // accepting filepaths
    if (event->mimeData()->hasFormat("text/uri-list"))
        event->acceptProposedAction();
}

void MainWindow::dragMoveEvent(QDragMoveEvent *event) {
    // accepting filepaths
    if (event->mimeData()->hasFormat("text/uri-list"))
        event->acceptProposedAction();
}

void MainWindow::dropEvent(QDropEvent *event) {
    QList<QUrl> uris;

    foreach (const QByteArray &uriData,
             event->mimeData()->data("text/uri-list").split('\n'))
        if (!uriData.isEmpty())
            uris << QUrl::fromEncoded(uriData).toLocalFile().remove('\r');

    if (!uris.isEmpty()) {
        QFileInfo fi(uris.first().toString());

        if (uris.size() > 1)
            DEVLOG_DEBUG("Multiple URIs dropped, currently using only first one");

        if (fi.exists() && !fi.isDir() &&
                !fi.suffix().isEmpty())  // if it's an existing file with some extension
        {
            // maybe it's a note/lyrics file? (ust/vsq/vsqx/midi)
            if (fi.suffix() == "ustj") {
                emit loadUST(fi.absoluteFilePath());
            } else
                DEVLOG_ERROR("File extension not supported: " + fi.suffix());
        }
    }
}

// autoconneccted slot
void MainWindow::on_actionPhoneme_Transformation_triggered() {
    _noteEditor->doPhonemeTransformation();
}

void MainWindow::onSingerSelected(int index) {
    _ctrl->selectSinger(_singerSelect->itemText(index));
    _doc->setSingerName(_singerSelect->itemText(index));
}

void MainWindow::onActionJackTriggered() {
    QAction *a = (QAction *)QObject::sender();
    _ctrl->setJackTranportEnabled(a->isChecked());
}

void MainWindow::markOverlappingNotes(quint64 id1, quint64 id2) {
    _noteEditor->setNoteColor(id1, EColor::red);
    _noteEditor->setNoteColor(id2, EColor::red);
    onLog("found overlapping notes", ELog::error);
    _noteEditor->update();
}

void MainWindow::updateNoteColors() {
    foreach (quint64 id, _noteEditor->getNoteIDs()) {
        _noteEditor->setNoteColor(id, EColor::none);
    }
}

void MainWindow::updateSetup() {
    // DEVLOG_DEBUG("MainWindow::updateSetup()");
    _piano->configure(_ns);
    _meter->configure2(_ns);
    _noteEditor->configure(_ns);
    if (_drawZone) _drawZone->configure(_ns);
}

void MainWindow::updateRecentFileActions() {
    QSettings settings;
    QStringList files = settings.value("recentFileList").toStringList();

    int numRecentFiles = qMin(files.size(), (int)MAXRECENTFILES);

    for (int i = 0; i < numRecentFiles; ++i) {
        QString fileName = QFileInfo(files[i]).fileName();
        QString text = tr("&%1 %2").arg(i + 1).arg(fileName);
        _recentFileActs[i]->setText(text);
        _recentFileActs[i]->setData(files[i]);
        _recentFileActs[i]->setVisible(true);
    }
    for (int j = numRecentFiles; j < MAXRECENTFILES; ++j)
        _recentFileActs[j]->setVisible(false);
}

void MainWindow::openRecentFile() {
    QAction *action = qobject_cast<QAction *>(sender());
    if (action) {
        QString ustFileName = action->data().toString();
        if (QFile(ustFileName).exists())
            loadUST(ustFileName);
        else {
            QSettings settings;
            QStringList files = settings.value("recentFileList").toStringList();
            files.removeOne(ustFileName);
            settings.setValue("recentFileList", files);
            updateRecentFileActions();

            QMessageBox msgBox;
            msgBox.setText("The file \"" + ustFileName + "\"does not exist.");
            msgBox.exec();
        }
    }
}

void MainWindow::on_actionSave_Last_Play_triggered() {
    QString lastPlay = _ctrl->lastPlay();
    DEVLOG_DEBUG("lastPlay: " + lastPlay);
    if (lastPlay.length()) {
        QString fileName = QFileInfo(lastPlay).fileName();

        QString fileNameCopy = QFileDialog::getSaveFileName(
                                   this, tr("Save Last Play"), _lastScoreDir, tr("WAV Files (*.wav)"));
        if (!fileNameCopy.isEmpty()) {
            if (!fileNameCopy.endsWith(".wav")) fileNameCopy += ".wav";
            DEVLOG_DEBUG("CP lastPlay: " + fileNameCopy);
            if (QFileInfo(fileNameCopy).exists()) QFile::remove(fileNameCopy);
            QFile::copy(lastPlay, fileNameCopy);
        }
    }
}

struct selectionRange MainWindow::getSelectionRange() {
    return _noteEditor->getSelectionRange();
}

void MainWindow::on_actionMusicXML_triggered() {
    QString dir = settings.value("last_musicxml_dir","/tmp").toString();
    QString fileName = QFileDialog::getOpenFileName(
                           this, tr("Import MusicXML"),
                           dir,  // FIXME do not hardcode
                           tr("MusicXML Files (*.xml)"));
    if(fileName.length()) {
        QFileInfo f(fileName);
        dir = f.dir().path();
        settings.setValue("last_musicxml_dir",dir);
        _doc->setDocName("imported "+f.fileName());
        _doc->importMusicXML(fileName);
    }


}

void MainWindow::on_actionUST_triggered() {
    QString dir = settings.value("last_ust_dir","/tmp").toString();

    QString fileName = QFileDialog::getOpenFileName(
                           this, tr("Import MusicXML"),
                           dir,
                           tr("UTAU Sequence Text Files (*.ust)"));
    {
        QFileInfo f(fileName);
        dir = f.dir().path();
        settings.setValue("last_ust_dir",dir);
        _doc->setDocName("imported "+f.fileName());
        _doc->importUST(fileName);
    }

}

void MainWindow::updateTMap() {
    QJsonArray array;
    _ns.tmap->toJson(array);
    _doc->setTempoMap(array);
    updateSetup();
}

void MainWindow::on_actionEdit_Tempo_Time_Signature_triggered() {
    _ns.tmap->beginEditing();
    TempoDialog dialog(_ns.tmap, this);
    if (dialog.exec() == QDialog::Accepted) {
        updateTMap();
    } else {
        // UNDO changes
        _ns.tmap->undo();
    }
}

void MainWindow::meter_barClicked(int bar, tmlabel mode) {
    _current_bar = bar;
    _current_mode = mode;
    if (mode == TM_BPM) {
        _mbl->setText("[" + STR(bar) + "] BPM=");
        _mbe->setText("120");  // FIXME
        _mbe->setFocus();
    } else {
        _mbl->setText("[" + STR(bar) + "] SIG=");
        _mbe->setText("4/4");
        _mbe->setFocus();
    }
}

void MainWindow::mbe_keyPressHooked(int) {
    QString text = _mbe->text();
    if (_current_mode == TM_BPM || _current_mode == TM_SIG) {
        if (_ns.tmap->setLabel(_current_mode, _current_bar - 1, text)) {
            updateTMap();
            _mbl->setText("");
            _mbe->setText("");
            _current_mode = TM_ALL;
        }

    } else {
        QStringList cmd = text.split(" ");
        if (cmd.length() == 2) {
            if (cmd[0] == "BPM") meter_barClicked(QVariant(cmd[1]).toInt(), TM_BPM);
            if (cmd[0] == "SIG") meter_barClicked(QVariant(cmd[1]).toInt(), TM_SIG);
        }
    }
}

void MainWindow::on_actionConnect2Jack_triggered()
{
    bool jack_auto_connect = ui->actionConnect2Jack->isChecked();
    settings.setValue("jack_auto_connect",jack_auto_connect);

    QMessageBox msgbox;
    msgbox.setText("jack_auto_connect changed. Please restart QTAU.");
    msgbox.exec();
}

void MainWindow::onSelectionChanged()
{
    auto sel = _noteEditor->getSelection();

    bool en = _doc->updateModel(_dynTableModel,sel);
    _dynTable->setEnabled(en);



}

